package archer.back.controllers

import java.util.UUID

import archer.back.models.CommonBody
import play.api.mvc.ControllerComponents
import utils.ArcherHelper
import io.circe.syntax._
import play.api.libs.ws.WSClient
import cats.implicits._
import io.circe.Json
import io.circe.generic.JsonCodec
import models.TableData
import org.xarcher.emiya.utils.EmbeddedServer
import com.sksamuel.elastic4s.http.ElasticDsl._
import com.sksamuel.elastic4s.circe._
import play.utils.UriEncoding

class ElasticSearch(
  controllerComponents: ControllerComponents,
  elasticService: ElasticService,
  planService: PlanService,
  embeddedServer: EmbeddedServer,
  wsClient: WSClient)
  extends BackController(controllerComponents)
  with ArcherHelper {

  implicit val ec = defaultExecutionContext

  def test02(id: Int) = Action.async { implicit request =>
    futureLess.lift {
      val j = futureLess.unlift(R.toJson(planService.test01Start(id)))
      Ok(j)
    }
  }

  def test03(
    wildcardSource: String,
    termSource: String,
    planId: Int,
    actionId: Int,
    page: Int,
    limit: Int) = Action.async { implicit request =>
    val result = etLess.lift {
      val wildcardSource1 = wildcardSource.trim
      val termSource1 = termSource.trim
      val wildcardList = if (!wildcardSource1.isEmpty) {
        List(wildcardQuery("subs.url", wildcardSource1))
      } else {
        List.empty
      }
      val termList = if (!termSource1.isEmpty) {
        List(termQuery("subs.url", termSource1))
      } else {
        List.empty
      }

      val queryAction = search(embeddedServer.index)
        .types(embeddedServer.typeName)
        .query(
          boolQuery().filter(
            termQuery("planId", planId),
            termQuery("actionId", actionId),
            nestedQuery("subs", boolQuery().must(wildcardList ::: termList))))
        .sourceExclude(
          List(
            "body",
            "cssBody",
            "textBody",
            "law_body",
            "law_text_body",
            "subs"))
        .size(limit)
        .from(limit * (page - 1))

      val responseT =
        etLess.unlift(commonCatch(embeddedServer.esLocalClient.flatMap {
          client =>
            client.execute(queryAction)
        }))
      val response = etLess.unlift(V.fromEither(responseT.left.map(r =>
        ErrorResult(code = r.status, msg = r.error.reason))))
      val responseModels = {
        response.result.to[CommonBody].zip(response.result.ids)
      }
      TableData(
        data = responseModels.map(s =>
          ("_id" -> Json.fromString(s._2)) +: s._1.asJsonObject),
        count = Option(response.result.totalHits.toInt))
    }
    R.toJsonResult(result)
  }

  @JsonCodec
  case class KeywordWrap(
    _id: String,
    commonBody: CommonBody,
    highlight: Option[String])

  def keywordSearch(wildcardKeyword: String, termSource: String, termKeywordOr: String, planId: Int, actionId: Int, page: Int, limit: Int) = Action.async { implicit request =>
    val result = etLess.lift {
      val termSource1 =
        termSource.split(' ').toList.map(_.trim).filterNot(_.isEmpty)
      val termKeywordOr1 =
        termKeywordOr.split(' ').toList.map(_.trim).filterNot(_.isEmpty)
      val wildcardSource1 =
        wildcardKeyword.split(' ').toList.map(_.trim).filterNot(_.isEmpty)

      val termList = termSource1.map(term => wildcardQuery("search_law_text_body", s"*${term}*"))
      val termList1 = termSource1.map(term => matchQuery("textBody", s"${term}"))

      val termOrList = termKeywordOr1.map(term => wildcardQuery("search_law_text_body", s"*${term}*"))
      val termOrList1 = termKeywordOr1.map(term => matchQuery("textBody", s"${term}"))

      val wildcardList =
        wildcardSource1.map(term => matchQuery("textBody", s"${term}"))
      val preTag = UUID.randomUUID.toString
      val postTag = UUID.randomUUID.toString

      val booleanQuery1 = boolQuery().filter(termQuery("planId", planId), termQuery("actionId", actionId))

      val booleanQuery2 = if ((termList ::: wildcardList).isEmpty)
        booleanQuery1
      else
        booleanQuery1.must(termList ::: wildcardList)

      val booleanQuery3 = if (termOrList.isEmpty)
        booleanQuery2
      else
        booleanQuery2.should(termOrList).minimumShouldMatch(1)

      val queryAction = search(embeddedServer.index)
        .types(embeddedServer.typeName)
        .query(booleanQuery3)
        .highlighting(
          highlightOptions()
            .highlightQuery(
              boolQuery()
                .must(termList1 ::: wildcardList)
                .should(termOrList1).minimumShouldMatch(1))
            .preTags(preTag)
            .postTags(postTag),
          highlight("textBody").fragmentSize(240).noMatchSize(240))
        .size(limit)
        .from(limit * (page - 1))
        .sourceExclude(
          List(
            "body",
            "cssBody",
            "textBody",
            "law_body",
            "law_text_body",
            "subs"))
      val responseT =
        etLess.unlift(commonCatch(embeddedServer.esLocalClient.flatMap {
          client =>
            client.execute(queryAction)
        }))
      val response = etLess.unlift(V.fromEither(responseT.left.map(r =>
        ErrorResult(code = r.status, msg = r.error.reason))))
      val responseModels = {
        response.result.hits.hits.toList.map { s =>
          KeywordWrap(
            _id = s.id,
            commonBody = s.to[CommonBody],
            highlight = s
              .highlightFragments("textBody")
              .headOption
              .map(
                s =>
                  play.twirl.api.HtmlFormat
                    .escape(s)
                    .body
                    .replaceAllLiterally(
                      preTag,
                      """<span style="color: red; font-weight: bold;">""")
                    .replaceAllLiterally(postTag, """</span>""")))
        }
      }
      TableData(
        data = responseModels,
        count = Option(response.result.totalHits.toInt))
    }
    R.toJsonResult(result)
  }

  def sourceKeywordSearch(wildcardKeyword: String, termSource: String, termKeywordOr: String, planId: Int, actionId: Int, page: Int, limit: Int) = Action.async { implicit request =>
    val result = etLess.lift {
      val termSource1 =
        termSource.split(' ').toList.map(_.trim).filterNot(_.isEmpty)
      val termKeywordOr1 =
        termKeywordOr.split(' ').toList.map(_.trim).filterNot(_.isEmpty)
      val wildcardSource1 =
        wildcardKeyword.split(' ').toList.map(_.trim).filterNot(_.isEmpty)

      val termList = termSource1.map(term => wildcardQuery("search_law_body", s"*${term}*"))
      val termList1 = termSource1.map(term => matchQuery("body", s"${term}"))

      val termOrList = termKeywordOr1.map(term => wildcardQuery("search_law_body", s"*${term}*"))
      val termOrList1 = termKeywordOr1.map(term => matchQuery("body", s"${term}"))

      val wildcardList =
        wildcardSource1.map(term => matchQuery("body", s"${term}"))
      val preTag = UUID.randomUUID.toString
      val postTag = UUID.randomUUID.toString

      val booleanQuery1 = boolQuery().filter(termQuery("planId", planId), termQuery("actionId", actionId))

      val booleanQuery2 = if ((termList ::: wildcardList).isEmpty)
        booleanQuery1
      else
        booleanQuery1.must(termList ::: wildcardList)

      val booleanQuery3 = if (termOrList.isEmpty)
        booleanQuery2
      else
        booleanQuery2.should(termOrList).minimumShouldMatch(1)

      val queryAction = search(embeddedServer.index)
        .types(embeddedServer.typeName)
        .query(booleanQuery3)
        .highlighting(
          highlightOptions()
            .highlightQuery(
              boolQuery()
                .must(termList1 ::: wildcardList)
                .should(termOrList1).minimumShouldMatch(1))
            .preTags(preTag)
            .postTags(postTag),
          highlight("body").fragmentSize(240).noMatchSize(240))
        .size(limit)
        .from(limit * (page - 1))
        .sourceExclude(
          List(
            "body",
            "cssBody",
            "textBody",
            "law_body",
            "law_text_body",
            "subs"))
      val responseT =
        etLess.unlift(commonCatch(embeddedServer.esLocalClient.flatMap {
          client =>
            client.execute(queryAction)
        }))
      val response = etLess.unlift(V.fromEither(responseT.left.map(r =>
        ErrorResult(code = r.status, msg = r.error.reason))))
      val responseModels = {
        response.result.hits.hits.toList.map { s =>
          KeywordWrap(
            _id = s.id,
            commonBody = s.to[CommonBody],
            highlight = s
              .highlightFragments("body")
              .headOption
              .map(
                s =>
                  play.twirl.api.HtmlFormat
                    .escape(s)
                    .body
                    .replaceAllLiterally(
                      preTag,
                      """<span style="color: red; font-weight: bold;">""")
                    .replaceAllLiterally(postTag, """</span>""")))
        }
      }
      TableData(
        data = responseModels,
        count = Option(response.result.totalHits.toInt))
    }
    R.toJsonResult(result)
  }

  def actionByTime(time: String, planId: Int) = Action.async {
    implicit request =>
      val result = etLess.lift {
        val action = etLess.unlift(
          planService.actionByTime(
            UriEncoding.decodePathSegment(time, "utf-8"),
            planId))
        TableData(data = action, count = Option(1))
      }
      R.toJsonResult(result)
  }

  def actions(planId: Int) = Action.async { implicit request =>
    R.toJsonResult(planService.actions(planId))
  }

  def actionIdById(actionId: Int) = Action.async { implicit request =>
    R.toJsonResult(planService.actionById(actionId))
  }

}
